use loco_rs::prelude::*;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use super::_entities::orders::{self, ActiveModel, Entity, Model};

#[derive(Debug, Deserialize, Serialize)]
pub struct CreateParams {
    pub number: String,
    pub customer_id: String,
    pub customer_group_id: String,
    pub shipping_address_id: String,
    pub billing_address_id: String,
    pub customer_name: String,
    pub email: String,
    pub calling_code: i32,
    pub telephone: String,
    pub total: Decimal,
    pub locale: String,
    pub currency_code: String,
    pub currency_value: String,
    pub ip: String,
    pub user_agent: String,
    pub status: String,
    pub shipping_method_code: String,
    pub shipping_method_name: String,
    pub shipping_customer_name: String,
    pub shipping_calling_code: String,
    pub shipping_telephone: String,
    pub shipping_country: String,
    pub shipping_country_id: String,
    pub shipping_state_id: String,
    pub shipping_state: String,
    pub shipping_city: String,
    pub shipping_address_1: String,
    pub shipping_address_2: String,
    pub shipping_zipcode: String,
    pub billing_method_code: String,
    pub billing_method_name: String,
    pub billing_customer_name: String,
    pub billing_calling_code: String,
    pub billing_telephone: String,
    pub billing_country: String,
    pub billing_country_id: String,
    pub billing_state_id: String,
    pub billing_state: String,
    pub billing_city: String,
    pub billing_address_1: String,
    pub billing_address_2: String,
    pub billing_zipcode: String,
    pub comment: Option<String>,
}

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 1, message = "Order number must not be empty."))]
    pub number: String,
    #[validate(length(min = 1, message = "Customer ID must not be empty."))]
    pub customer_id: String,
    #[validate(length(min = 1, message = "Customer name must not be empty."))]
    pub customer_name: String,
    #[validate(email(message = "Email must be valid."))]
    pub email: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            number: self.number.as_ref().to_owned(),
            customer_id: self.customer_id.as_ref().to_owned(),
            customer_name: self.customer_name.as_ref().to_owned(),
            email: self.email.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for super::_entities::orders::ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = ActiveValue::Set(Uuid::new_v4().to_string());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds an order by the provided id
    ///
    /// # Errors
    ///
    /// When could not find order or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let order = orders::Entity::find()
            .filter(model::query::condition().eq(orders::Column::Id, id).build())
            .one(db)
            .await?;
        order.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds an order by the provided order number
    ///
    /// # Errors
    ///
    /// When could not find order or DB query error
    pub async fn find_by_number(db: &DatabaseConnection, number: &str) -> ModelResult<Self> {
        let order = orders::Entity::find()
            .filter(
                model::query::condition()
                    .eq(orders::Column::Number, number)
                    .build(),
            )
            .one(db)
            .await?;
        order.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds orders by the provided customer id
    ///
    /// # Errors
    ///
    /// When could not find orders or DB query error
    pub async fn find_by_customer_id(
        db: &DatabaseConnection,
        customer_id: &str,
    ) -> ModelResult<Vec<Self>> {
        let orders = orders::Entity::find()
            .filter(
                model::query::condition()
                    .eq(orders::Column::CustomerId, customer_id)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(orders)
    }

    /// Asynchronously creates an order and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the order into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateParams) -> ModelResult<Self> {
        let txn = db.begin().await?;

        if orders::Entity::find()
            .filter(
                model::query::condition()
                    .eq(orders::Column::Number, &params.number)
                    .build(),
            )
            .one(&txn)
            .await?
            .is_some()
        {
            return Err(ModelError::EntityAlreadyExists {});
        }

        let order = orders::ActiveModel {
            number: ActiveValue::set(params.number.to_string()),
            customer_id: ActiveValue::set(params.customer_id.to_string()),
            customer_group_id: ActiveValue::set(params.customer_group_id.to_string()),
            shipping_address_id: ActiveValue::set(params.shipping_address_id.to_string()),
            billing_address_id: ActiveValue::set(params.billing_address_id.to_string()),
            customer_name: ActiveValue::set(params.customer_name.to_string()),
            email: ActiveValue::set(params.email.to_string()),
            calling_code: ActiveValue::set(params.calling_code),
            telephone: ActiveValue::set(params.telephone.to_string()),
            total: ActiveValue::set(params.total),
            locale: ActiveValue::set(params.locale.to_string()),
            currency_code: ActiveValue::set(params.currency_code.to_string()),
            currency_value: ActiveValue::set(params.currency_value.to_string()),
            ip: ActiveValue::set(params.ip.to_string()),
            user_agent: ActiveValue::set(params.user_agent.to_string()),
            status: ActiveValue::set(params.status.to_string()),
            shipping_method_code: ActiveValue::set(params.shipping_method_code.to_string()),
            shipping_method_name: ActiveValue::set(params.shipping_method_name.to_string()),
            shipping_customer_name: ActiveValue::set(params.shipping_customer_name.to_string()),
            shipping_calling_code: ActiveValue::set(params.shipping_calling_code.to_string()),
            shipping_telephone: ActiveValue::set(params.shipping_telephone.to_string()),
            shipping_country: ActiveValue::set(params.shipping_country.to_string()),
            shipping_country_id: ActiveValue::set(params.shipping_country_id.to_string()),
            shipping_state_id: ActiveValue::set(params.shipping_state_id.to_string()),
            shipping_state: ActiveValue::set(params.shipping_state.to_string()),
            shipping_city: ActiveValue::set(params.shipping_city.to_string()),
            shipping_address_1: ActiveValue::set(params.shipping_address_1.to_string()),
            shipping_address_2: ActiveValue::set(params.shipping_address_2.to_string()),
            shipping_zipcode: ActiveValue::set(params.shipping_zipcode.to_string()),
            billing_method_code: ActiveValue::set(params.billing_method_code.to_string()),
            billing_method_name: ActiveValue::set(params.billing_method_name.to_string()),
            billing_customer_name: ActiveValue::set(params.billing_customer_name.to_string()),
            billing_calling_code: ActiveValue::set(params.billing_calling_code.to_string()),
            billing_telephone: ActiveValue::set(params.billing_telephone.to_string()),
            billing_country: ActiveValue::set(params.billing_country.to_string()),
            billing_country_id: ActiveValue::set(params.billing_country_id.to_string()),
            billing_state_id: ActiveValue::set(params.billing_state_id.to_string()),
            billing_state: ActiveValue::set(params.billing_state.to_string()),
            billing_city: ActiveValue::set(params.billing_city.to_string()),
            billing_address_1: ActiveValue::set(params.billing_address_1.to_string()),
            billing_address_2: ActiveValue::set(params.billing_address_2.to_string()),
            billing_zipcode: ActiveValue::set(params.billing_zipcode.to_string()),
            comment: ActiveValue::set(params.comment.clone()),
            ..Default::default()
        }
        .insert(&txn)
        .await?;

        txn.commit().await?;

        Ok(order)
    }
}

impl ActiveModel {
    /// Updates the order's status
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_status(
        mut self,
        db: &DatabaseConnection,
        status: String,
    ) -> ModelResult<Model> {
        self.status = ActiveValue::set(status);
        Ok(self.update(db).await?)
    }

    /// Soft deletes the order
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn soft_delete(mut self, db: &DatabaseConnection) -> ModelResult<Model> {
        self.deleted_at = ActiveValue::set(Some(chrono::Utc::now().into()));
        Ok(self.update(db).await?)
    }
}
